﻿#Region "Imports"
Imports System.IO
Imports Microsoft.LightSwitch.Presentation
Imports Microsoft.LightSwitch.Presentation.Extensions
Imports Microsoft.LightSwitch.Client
Imports Microsoft.LightSwitch
Imports Microsoft.LightSwitch.Model
Imports Microsoft.LightSwitch.Details
Imports System.Runtime.CompilerServices
Imports Microsoft.LightSwitch.Threading
Imports System.Diagnostics
Imports Microsoft.LightSwitch.Framework
Imports System.Reflection
Imports System.Security
Imports System.Runtime.InteropServices
Imports System.Linq.Expressions
Imports Microsoft.LightSwitch.Sdk.Proxy
Imports Microsoft.VisualStudio.ExtensibilityHosting
Imports Microsoft.LightSwitch.Runtime.Shell.Framework
Imports System.Runtime.InteropServices.Automation
Imports System.Collections
Imports System.Windows.Media.Imaging
#End Region

Public Module Word

    Public Function GenerateDocument(Template As String, Item As IEntityObject, ColumnMappings As List(Of ColumnMapping)) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()

        wordProxy.CreateWord()
        wordProxy.OpenDocument(Template)
        PopulateContentControls(ColumnMappings, Item, wordProxy)
        doc = wordProxy.Document
        wordProxy.ShowDocument()

        GenerateDocument = doc
    End Function

#Region "Export Overloads"
    ' Exports an IVisualCollection to a table in either the active (UseActiveDocument = True) or a new document (UseActiveDocument = False)
    Public Function Export(Collection As IVisualCollection, UseActiveDocument As Boolean) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()
        Dim bUseActiveDocument As Boolean = False
        Dim rg As Object = Nothing

        ' if Word is active then use it
        If wordProxy.GetWord() Then
            ' obtain a reference to the selection range
            If UseActiveDocument Then
                rg = wordProxy.Word.Selection.Range
                bUseActiveDocument = True
            Else
                wordProxy.CreateDocument()
            End If
        Else
            wordProxy.CreateWord()
            wordProxy.CreateDocument()
        End If

        Dim entityObj As IEntityObject
        Dim columnNames As List(Of String) = New List(Of String)

        If Collection.Count > 0 Then

            ' get column properties
            Dim columnProperties As IEnumerable(Of IEntityProperty) = Collection(0).Details.Properties.All()
            Dim entityProperty As IEntityProperty
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = 1

            ' add table
            Dim oTable As Object
            If bUseActiveDocument Then
                oTable = wordProxy.AddTable(Collection.Count + 1, columnProperties.Count, rg)
            Else
                oTable = wordProxy.AddTable(Collection.Count + 1, columnProperties.Count)
            End If


            ' add columns names to the list
            For Each entityProperty In columnProperties
                columnNames.Add(entityProperty.Name)

                ' add column headers to table
                wordProxy.SetTableCell(oTable, 1, columnCounter, entityProperty.DisplayName)
                columnCounter += 1
            Next

            ' add values on the row following the headers
            rowCounter = 2

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To columnNames.Count - 1
                    Dim sValue As String = LightSwitchHelper.GetValue(entityObj, columnNames(i))
                    ' if it's an image, export it
                    If sValue = "(Image)" Then
                        wordProxy.SetTableCell(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, columnNames(i)))
                    Else
                        wordProxy.SetTableCell(oTable, rowCounter, i + 1, sValue)
                    End If
                Next
                rowCounter += 1
            Next

        End If

        doc = wordProxy.Document
        wordProxy.ShowDocument()

        Export = doc
    End Function

    ' Exports an IVisualCollection to a table in either the active (UseActiveDocument = True) or a new document (UseActiveDocument = False)
    Public Function Export(Collection As IVisualCollection, BuildColumnHeadings As Boolean, ColumnNames As List(Of ColumnMapping), UseActiveDocument As Boolean) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()
        Dim bUseActiveDocument As Boolean = False
        Dim rg As Object = Nothing

        ' if Word is active then use it
        If wordProxy.GetWord() Then
            ' obtain a reference to the selection range
            If UseActiveDocument Then
                rg = wordProxy.Word.Selection.Range
                bUseActiveDocument = True
            Else
                wordProxy.CreateDocument()
            End If
        Else
            wordProxy.CreateWord()
            wordProxy.CreateDocument()
        End If

        Dim entityObj As IEntityObject

        If Collection.Count > 0 Then

            ' get column properties
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = 1

            ' add table
            Dim oTable As Object
            If bUseActiveDocument Then
                oTable = wordProxy.AddTable(Collection.Count + 1, ColumnNames.Count, rg)
            Else
                oTable = wordProxy.AddTable(Collection.Count + 1, ColumnNames.Count)
            End If

            ' add columns names to the list
            Dim map As ColumnMapping
            For Each map In ColumnNames
                If columnCounter > oTable.Columns.Count Then
                    oTable.Columns.Add()
                End If
                If BuildColumnHeadings Then
                    If map.TableField.DisplayName.Length > 0 Then
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.DisplayName
                    Else
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.Name
                    End If
                End If
                columnCounter += 1
            Next

            ' add values on the row following the headers
            If BuildColumnHeadings Then
                rowCounter += 1
            End If

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To ColumnNames.Count - 1
                    Dim sValue As String = LightSwitchHelper.GetValue(entityObj, ColumnNames(i).TableField.Name)
                    ' if it's an image, export it
                    If sValue = "(Image)" Then
                        wordProxy.SetTableCell(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, ColumnNames(i).TableField.Name))
                    Else
                        wordProxy.SetTableCell(oTable, rowCounter, i + 1, sValue)
                    End If
                Next
                rowCounter += 1
            Next

        End If

        doc = wordProxy.Document
        wordProxy.ShowDocument()

        Export = doc
    End Function

    ' Exports a collection to a table in a Word document located at DocumentPath. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IVisualCollection) As Object
        Dim doc As Object
        Dim word As Object
        Dim result As Object = Nothing

        Try
            word = GetWord()
            If Not word Is Nothing Then
                doc = GetDocument(word, DocumentPath)
                If Not doc Is Nothing Then
                    result = Export(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection)
                    word.Visible = True
                Else
                    Throw New System.Exception("Could not open the document '" & DocumentPath & "'.")
                End If
            Else
                Throw New System.Exception("Could not obtain a reference to Microsoft Word.")
            End If
        Catch ex As Exception
            Throw ex
        End Try

        Export = result
    End Function

    ' Exports a collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IVisualCollection) As Object
        Dim entityObj As IEntityObject
        Dim columnNames As List(Of String) = New List(Of String)

        If Collection.Count > 0 Then

            ' get column properties
            Dim columnProperties As IEnumerable(Of IEntityProperty) = Collection(0).Details.Properties.All()
            Dim entityProperty As IEntityProperty
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = StartRow

            ' validate that the Documnet argument is expected type of Word.Document
            If Not IsWordDocumentObject(Document) Then
                Throw New System.ArgumentException("'Document' is not the expected type of object. Expected object should be a Word.Document object.", "Document")
            End If

            ' validate the BookmarkName argument
            If Not IsValidBookmark(Document, BookmarkName) Then
                Throw New System.ArgumentException("'BookmarkName' was not found in 'Document'", "BookmarkName")
            End If

            ' validate that the bookmark is part of a table
            If Document.Bookmarks(BookmarkName).Range.Tables.Count = 0 Then
                Throw New System.ArgumentException("No table was found at the bookmark", "BookmarkName")
            End If

            ' add table
            Dim oTable As Object
            oTable = Document.Bookmarks(BookmarkName).Range.Tables(1)

            ' validate the StartRow argument
            If StartRow > oTable.Rows.Count Then
                Throw New System.ArgumentException("'StartRow' is greater then the number of rows in the table", "StartRow")
            End If

            ' add columns names to the list
            For Each entityProperty In columnProperties
                columnNames.Add(entityProperty.Name)
                If columnCounter > oTable.Columns.Count Then
                    oTable.Columns.Add()
                End If
                If BuildColumnHeadings Then
                    oTable.Cell(rowCounter, columnCounter).Range.Text = entityProperty.DisplayName
                End If
                columnCounter += 1
            Next

            ' add values on the row following the headers
            If BuildColumnHeadings Then
                rowCounter += 1
            End If

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To columnNames.Count - 1
                    If rowCounter > oTable.Rows.Count Then
                        oTable.Rows.Add()
                    End If
                    Dim sValue As String = LightSwitchHelper.GetValue(entityObj, columnNames(i))
                    ' if it's an image, export it
                    If sValue = "(Image)" Then
                        SetTableCellImage(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, columnNames(i)))
                    Else
                        oTable.Cell(rowCounter, i + 1).Range.Text = sValue
                    End If

                Next
                rowCounter += 1
            Next

        Else
            ' No items in the collection
        End If

        Export = Document
    End Function

    ' Exports a collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IVisualCollection, ColumnNames As List(Of String)) As Object
        Dim mappings As New List(Of ColumnMapping)
        Dim map As ColumnMapping
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()
        Dim fd As FieldDefinition

        ' if Word is active then use it
        If Not wordProxy.GetWord() Then
            If Not wordProxy.CreateWord() Then
                Throw New System.Exception("Could not start Microsoft Word.")
            End If
        End If

        wordProxy.OpenDocument(DocumentPath)
        doc = wordProxy.Document

        For Each name As String In ColumnNames
            fd = GetFieldDefinition(Collection, name)
            map = New ColumnMapping("", name)
            If fd Is Nothing Then
                map.TableField.DisplayName = name
            Else
                map.TableField = fd
            End If
            mappings.Add(map)
        Next

        Export = Export(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection, mappings)
        wordProxy.ShowDocument()
    End Function

    ' Exports a collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IVisualCollection, ColumnNames As List(Of ColumnMapping)) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()

        ' if Word is active then use it
        If Not wordProxy.GetWord() Then
            If Not wordProxy.CreateWord() Then
                Throw New System.Exception("Could not start Microsoft Word.")
            End If
        End If

        wordProxy.OpenDocument(DocumentPath)
        doc = wordProxy.Document

        Export = Export(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection, ColumnNames)
        wordProxy.ShowDocument()
    End Function

    ' Exports a collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IVisualCollection, ColumnNames As List(Of String)) As Object
        Dim mappings As New List(Of ColumnMapping)
        Dim map As ColumnMapping
        Dim fd As FieldDefinition

        For Each name As String In ColumnNames
            fd = GetFieldDefinition(Collection, name)
            map = New ColumnMapping("", name)
            If fd Is Nothing Then
                map.TableField.DisplayName = name
            Else
                map.TableField = fd
            End If
            mappings.Add(map)
        Next

        Export = Export(Document, BookmarkName, StartRow, BuildColumnHeadings, Collection, mappings)
    End Function

    ' Exports an IVisualCollection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IVisualCollection, ColumnNames As List(Of ColumnMapping)) As Object
        Dim entityObj As IEntityObject

        If Collection.Count > 0 Then

            ' get column properties
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = StartRow

            ' validate that the Documnet argument is expected type of Word.Document
            If Not IsWordDocumentObject(Document) Then
                Throw New System.ArgumentException("'Document' is not the expected type of object. Expected object should be a Word.Document object.", "Document")
            End If

            ' validate the BookmarkName argument
            If Not IsValidBookmark(Document, BookmarkName) Then
                Throw New System.ArgumentException("'BookmarkName' was not found in 'Document'", "BookmarkName")
            End If

            ' validate that the bookmark is part of a table
            If Document.Bookmarks(BookmarkName).Range.Tables.Count = 0 Then
                Throw New System.ArgumentException("No table was found at the bookmark", "BookmarkName")
            End If

            ' add table
            Dim oTable As Object
            oTable = Document.Bookmarks(BookmarkName).Range.Tables(1)

            ' validate the StartRow argument
            If StartRow > oTable.Rows.Count Then
                Throw New System.ArgumentException("'StartRow' is greater then the number of rows in the table", "StartRow")
            End If

            ' add columns names to the list
            Dim map As ColumnMapping
            For Each map In ColumnNames
                If columnCounter > oTable.Columns.Count Then
                    oTable.Columns.Add()
                End If
                If BuildColumnHeadings Then
                    If map.TableField.DisplayName.Length > 0 Then
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.DisplayName
                    Else
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.Name
                    End If
                End If
                columnCounter += 1
            Next

            ' add values on the row following the headers
            If BuildColumnHeadings Then
                rowCounter += 1
            End If

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To ColumnNames.Count - 1
                    If rowCounter > oTable.Rows.Count Then
                        oTable.Rows.Add()
                    End If
                    Try
                        Dim sValue As Object

                        ' passed in a static value so use that instead
                        If ColumnNames(i).TableField.StaticValue IsNot Nothing Then
                            sValue = ColumnNames(i).TableField.StaticValue.ToString()
                        Else
                            sValue = LightSwitchHelper.GetValue(entityObj, ColumnNames(i).TableField.Name)

                            ' check to see if we have to format the value
                            If ColumnNames(i).TableField.FormatDelegate IsNot Nothing Then
                                Try
                                    ' Catch possible improperly formatted delegates
                                    Dim paramType As Type = ColumnNames(i).TableField.FormatDelegate.Method.GetParameters()(0).ParameterType
                                    sValue = ColumnNames(i).TableField.FormatDelegate.DynamicInvoke(Convert.ChangeType(sValue, paramType, Nothing)).ToString()
                                Catch ex As Exception
                                End Try
                            End If
                        End If

                        ' if it's an image, export it
                        If sValue = "(Image)" Then
                            SetTableCellImage(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, ColumnNames(i).TableField.Name))
                        Else
                            oTable.Cell(rowCounter, i + 1).Range.Text = sValue
                        End If
                    Catch ex As Exception
                        Throw ex
                    End Try
                Next
                rowCounter += 1
            Next

        Else
            ' No items in the collection
        End If

        Export = Document
    End Function

#End Region

#Region "ExportEntityCollection Overloads"

    ' Exports an IEntityCollection to a table in either the active (UseActiveDocument = True) or a new document (UseActiveDocument = False)
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(Collection As IEntityCollection, UseActiveDocument As Boolean) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()
        Dim bUseActiveDocument As Boolean = False
        Dim rg As Object = Nothing

        ' if Word is active then use it
        If wordProxy.GetWord() Then
            ' obtain a reference to the selection range
            If UseActiveDocument Then
                rg = wordProxy.Word.Selection.Range
                bUseActiveDocument = True
            Else
                wordProxy.CreateDocument()
            End If
        Else
            wordProxy.CreateWord()
            wordProxy.CreateDocument()
        End If

        Dim entityObj As IEntityObject
        Dim columnNames As List(Of String) = New List(Of String)

        ' get column properties
        Dim columnProperties As IEnumerable(Of IEntityProperty) = Collection(0).Details.Properties.All()
        Dim entityProperty As IEntityProperty
        Dim columnCounter As Integer = 1
        Dim rowCounter As Integer = 1

        ' add table
        Dim oTable As Object
        If bUseActiveDocument Then
            oTable = wordProxy.AddTable(1, columnProperties.Count, rg)
        Else
            oTable = wordProxy.AddTable(1, columnProperties.Count)
        End If


        ' add columns names to the list
        For Each entityProperty In columnProperties
            columnNames.Add(entityProperty.Name)

            ' add column headers to table
            wordProxy.SetTableCell(oTable, 1, columnCounter, entityProperty.DisplayName)
            columnCounter += 1
        Next

        ' add values on the row following the headers
        rowCounter = 2

        ' iterate the collection and extract values by column name
        For Each entityObj In Collection
            oTable.Rows.Add()
            For i As Integer = 0 To columnNames.Count - 1
                wordProxy.SetTableCell(oTable, rowCounter, i + 1, LightSwitchHelper.GetValue(entityObj, columnNames(i)))
            Next
            rowCounter += 1
        Next


        doc = wordProxy.Document
        wordProxy.ShowDocument()

        ExportEntityCollection = doc
    End Function

    ' Exports an IEntityCollection to a table in a Word document located at DocumentPath. BookmarkName is the name of the bookmark associated
    ' with the table.
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEntityCollection) As Object
        Dim doc As Object
        Dim word As Object
        Dim result As Object = Nothing

        Try
            word = GetWord()
            If Not word Is Nothing Then
                doc = GetDocument(word, DocumentPath)
                If Not doc Is Nothing Then
                    result = ExportEntityCollection(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection)
                    word.Visible = True
                Else
                    Throw New System.Exception("Could not open the document '" & DocumentPath & "'.")
                End If
            Else
                Throw New System.Exception("Could not obtain a reference to Microsoft Word.")
            End If
        Catch ex As Exception
            Throw ex
        End Try

        ExportEntityCollection = result
    End Function

    ' Exports an IEntityCollection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEntityCollection) As Object
        Dim entityObj As IEntityObject
        Dim columnNames As List(Of String) = New List(Of String)

        ' get column properties
        Dim columnProperties As IEnumerable(Of IEntityProperty) = Collection(0).Details.Properties.All()
        Dim entityProperty As IEntityProperty
        Dim columnCounter As Integer = 1
        Dim rowCounter As Integer = StartRow

        ' validate that the Documnet argument is expected type of Word.Document
        If Not IsWordDocumentObject(Document) Then
            Throw New System.ArgumentException("'Document' is not the expected type of object. Expected object should be a Word.Document object.", "Document")
        End If

        ' validate the BookmarkName argument
        If Not IsValidBookmark(Document, BookmarkName) Then
            Throw New System.ArgumentException("'BookmarkName' was not found in 'Document'", "BookmarkName")
        End If

        ' validate that the bookmark is part of a table
        If Document.Bookmarks(BookmarkName).Range.Tables.Count = 0 Then
            Throw New System.ArgumentException("No table was found at the bookmark", "BookmarkName")
        End If

        ' add table
        Dim oTable As Object
        oTable = Document.Bookmarks(BookmarkName).Range.Tables(1)

        ' validate the StartRow argument
        If StartRow > oTable.Rows.Count Then
            Throw New System.ArgumentException("'StartRow' is greater then the number of rows in the table", "StartRow")
        End If

        ' add columns names to the list
        For Each entityProperty In columnProperties
            columnNames.Add(entityProperty.Name)
            If columnCounter > oTable.Columns.Count Then
                oTable.Columns.Add()
            End If
            If BuildColumnHeadings Then
                oTable.Cell(rowCounter, columnCounter).Range.Text = entityProperty.DisplayName
            End If
            columnCounter += 1
        Next

        ' add values on the row following the headers
        If BuildColumnHeadings Then
            rowCounter += 1
        End If

        ' iterate the collection and extract values by column name
        For Each entityObj In Collection
            For i As Integer = 0 To columnNames.Count - 1
                If rowCounter > oTable.Rows.Count Then
                    oTable.Rows.Add()
                End If
                oTable.Cell(rowCounter, i + 1).Range.Text = LightSwitchHelper.GetValue(entityObj, columnNames(i))
            Next
            rowCounter += 1
        Next

        ExportEntityCollection = Document
    End Function

    ' Exports an IEntityCollection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEntityCollection, ColumnNames As List(Of String)) As Object
        Dim mappings As New List(Of ColumnMapping)
        Dim map As ColumnMapping

        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()

        ' if Word is active then use it
        If Not wordProxy.GetWord() Then
            If Not wordProxy.CreateWord() Then
                Throw New System.Exception("Could not start Microsoft Word.")
            End If
        End If

        wordProxy.OpenDocument(DocumentPath)
        doc = wordProxy.Document

        For Each name As String In ColumnNames
            map = New ColumnMapping("", name)
            map.TableField.DisplayName = name
            mappings.Add(map)
        Next

        ExportEntityCollection = ExportEntityCollection(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection, mappings)
        wordProxy.ShowDocument()
    End Function

    ' Exports an IEntityCollection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEntityCollection, ColumnNames As List(Of ColumnMapping)) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()

        ' if Word is active then use it
        If Not wordProxy.GetWord() Then
            If Not wordProxy.CreateWord() Then
                Throw New System.Exception("Could not start Microsoft Word.")
            End If
        End If

        wordProxy.OpenDocument(DocumentPath)
        doc = wordProxy.Document

        ExportEntityCollection = ExportEntityCollection(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection, ColumnNames)
        wordProxy.ShowDocument()
    End Function

    ' Exports an IEntityCollection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEntityCollection, ColumnNames As List(Of String)) As Object
        Dim mappings As New List(Of ColumnMapping)
        Dim map As ColumnMapping

        For Each name As String In ColumnNames
            map = New ColumnMapping("", name)
            map.TableField.DisplayName = name
            mappings.Add(map)
        Next

        ExportEntityCollection = ExportEntityCollection(Document, BookmarkName, StartRow, BuildColumnHeadings, Collection, mappings)
    End Function

    ' Exports an IEntityCollection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    <Obsolete("This method has been deprecated. Please use Word.Export() overloads.")>
    Public Function ExportEntityCollection(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEntityCollection, ColumnNames As List(Of ColumnMapping)) As Object
        Dim entityObj As IEntityObject

        ' get column properties
        Dim columnCounter As Integer = 1
        Dim rowCounter As Integer = StartRow

        ' validate that the Documnet argument is expected type of Word.Document
        If Not IsWordDocumentObject(Document) Then
            Throw New System.ArgumentException("'Document' is not the expected type of object. Expected object should be a Word.Document object.", "Document")
        End If

        ' validate the BookmarkName argument
        If Not IsValidBookmark(Document, BookmarkName) Then
            Throw New System.ArgumentException("'BookmarkName' was not found in 'Document'", "BookmarkName")
        End If

        ' validate that the bookmark is part of a table
        If Document.Bookmarks(BookmarkName).Range.Tables.Count = 0 Then
            Throw New System.ArgumentException("No table was found at the bookmark", "BookmarkName")
        End If

        ' add table
        Dim oTable As Object
        oTable = Document.Bookmarks(BookmarkName).Range.Tables(1)

        ' validate the StartRow argument
        If StartRow > oTable.Rows.Count Then
            Throw New System.ArgumentException("'StartRow' is greater then the number of rows in the table", "StartRow")
        End If

        ' add columns names to the list
        Dim map As ColumnMapping
        For Each map In ColumnNames
            If columnCounter > oTable.Columns.Count Then
                oTable.Columns.Add()
            End If
            If BuildColumnHeadings Then
                If map.TableField.DisplayName.Length > 0 Then
                    oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.DisplayName
                Else
                    oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.Name
                End If
            End If
            columnCounter += 1
        Next

        ' add values on the row following the headers
        If BuildColumnHeadings Then
            rowCounter += 1
        End If

        ' iterate the collection and extract values by column name
        For Each entityObj In Collection
            For i As Integer = 0 To ColumnNames.Count - 1
                If rowCounter > oTable.Rows.Count Then
                    oTable.Rows.Add()
                End If
                Try
                    Dim sValue As Object

                    ' passed in a static value so use that instead
                    If ColumnNames(i).TableField.StaticValue IsNot Nothing Then
                        sValue = ColumnNames(i).TableField.StaticValue.ToString()
                    Else
                        sValue = LightSwitchHelper.GetValue(entityObj, ColumnNames(i).TableField.Name)

                        ' check to see if we have to format the value
                        If ColumnNames(i).TableField.FormatDelegate IsNot Nothing Then
                            Try
                                ' Catch possible improperly formatted delegates
                                Dim paramType As Type = ColumnNames(i).TableField.FormatDelegate.Method.GetParameters()(0).ParameterType
                                sValue = ColumnNames(i).TableField.FormatDelegate.DynamicInvoke(Convert.ChangeType(sValue, paramType, Nothing)).ToString()
                            Catch ex As Exception
                            End Try
                        End If
                    End If

                    oTable.Cell(rowCounter, i + 1).Range.Text = sValue
                Catch ex As Exception
                    Throw ex
                End Try
            Next
            rowCounter += 1
        Next

        ExportEntityCollection = Document
    End Function

#End Region

#Region "IEnumerable Overloads"
    ' Exports an IEnumerable to a table in either the active (UseActiveDocument = True) or a new document (UseActiveDocument = False)
    Public Function Export(Collection As IEnumerable, UseActiveDocument As Boolean) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()
        Dim bUseActiveDocument As Boolean = False
        Dim rg As Object = Nothing
        Dim count As Integer = 0

        ' if Word is active then use it
        If wordProxy.GetWord() Then
            ' obtain a reference to the selection range
            If UseActiveDocument Then
                rg = wordProxy.Word.Selection.Range
                bUseActiveDocument = True
            Else
                wordProxy.CreateDocument()
            End If
        Else
            wordProxy.CreateWord()
            wordProxy.CreateDocument()
        End If

        Dim entityObj As Object

        count = (From c In Collection
                Select c).Count()

        If count > 0 Then

            ' get column properties
            Dim columnNames As List(Of String) = New List(Of String)
            Dim columnName As String
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = 1

            Dim columnProperties As IEnumerable(Of System.Reflection.PropertyInfo) = Collection(0).GetType().GetProperties().AsEnumerable()
            Dim entityProperty As System.Reflection.PropertyInfo

            For Each entityProperty In columnProperties
                columnNames.Add(entityProperty.Name)
            Next

            ' add table
            Dim oTable As Object
            If bUseActiveDocument Then
                oTable = wordProxy.AddTable(count + 1, columnNames.Count, rg)
            Else
                oTable = wordProxy.AddTable(count + 1, columnNames.Count)
            End If


            ' add columns names to the table
            For Each columnName In columnNames
                ' add column headers to table
                wordProxy.SetTableCell(oTable, 1, columnCounter, columnName)
                columnCounter += 1
            Next

            ' add values on the row following the headers
            rowCounter = 2

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To columnNames.Count - 1
                    Try
                        If TypeOf entityObj Is IBusinessObject Then
                            Dim sValue As String = LightSwitchHelper.GetValue(entityObj, columnNames(i))
                            ' if it's an image, export it
                            If sValue = "(Image)" Then
                                wordProxy.SetTableCell(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, columnNames(i)))
                            Else
                                wordProxy.SetTableCell(oTable, rowCounter, i + 1, sValue)
                            End If
                        Else
                            wordProxy.SetTableCell(oTable, rowCounter, i + 1, LightSwitchHelper.GetObjectValue(entityObj, columnNames(i)).ToString())
                        End If
                    Catch ex As Exception
                        wordProxy.SetTableCell(oTable, rowCounter, i + 1, entityObj.GetType().ToString())
                    End Try

                Next
                rowCounter += 1
            Next

        End If

        doc = wordProxy.Document
        wordProxy.ShowDocument()

        Export = doc
    End Function

    ' Exports an IEnumerable to a table in either the active (UseActiveDocument = True) or a new document (UseActiveDocument = False)
    Public Function Export(Collection As IEnumerable, BuildColumnHeadings As Boolean, ColumnNames As List(Of String), UseActiveDocument As Boolean) As Object
        Dim mappings As New List(Of ColumnMapping)
        Dim map As ColumnMapping

        For Each name As String In ColumnNames
            map = New ColumnMapping("", name)
            mappings.Add(map)
        Next

        Export = Export(Collection, BuildColumnHeadings, mappings, UseActiveDocument)
    End Function

    ' Exports an IEnumerable to a table in either the active (UseActiveDocument = True) or a new document (UseActiveDocument = False)
    Public Function Export(Collection As IEnumerable, BuildColumnHeadings As Boolean, ColumnNames As List(Of ColumnMapping), UseActiveDocument As Boolean) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()
        Dim bUseActiveDocument As Boolean = False
        Dim rg As Object = Nothing
        Dim count As Integer = 0

        ' if Word is active then use it
        If wordProxy.GetWord() Then
            ' obtain a reference to the selection range
            If UseActiveDocument Then
                rg = wordProxy.Word.Selection.Range
                bUseActiveDocument = True
            Else
                wordProxy.CreateDocument()
            End If
        Else
            wordProxy.CreateWord()
            wordProxy.CreateDocument()
        End If

        Dim entityObj As Object

        count = (From c In Collection
                Select c).Count()

        If count > 0 Then

            ' get column properties
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = 1

            ' add table
            Dim oTable As Object
            If bUseActiveDocument Then
                oTable = wordProxy.AddTable(count + 1, ColumnNames.Count, rg)
            Else
                oTable = wordProxy.AddTable(count + 1, ColumnNames.Count)
            End If


            ' add columns names to the list
            Dim map As ColumnMapping
            For Each map In ColumnNames
                If columnCounter > oTable.Columns.Count Then
                    oTable.Columns.Add()
                End If
                If BuildColumnHeadings Then
                    If map.TableField.DisplayName.Length > 0 Then
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.DisplayName
                    Else
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.Name
                    End If
                End If
                columnCounter += 1
            Next

            ' add values on the row following the headers
            rowCounter = 2

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To ColumnNames.Count - 1
                    If rowCounter > oTable.Rows.Count Then
                        oTable.Rows.Add()
                    End If
                    Try
                        Dim sValue As Object

                        ' passed in a static value so use that instead
                        If ColumnNames(i).TableField.StaticValue IsNot Nothing Then
                            sValue = ColumnNames(i).TableField.StaticValue.ToString()
                        Else
                            If TypeOf entityObj Is IBusinessObject Then
                                sValue = LightSwitchHelper.GetValue(entityObj, ColumnNames(i).TableField.Name)
                            Else
                                sValue = LightSwitchHelper.GetObjectValue(entityObj, ColumnNames(i).TableField.Name)
                            End If

                            ' check to see if we have to format the value
                            If ColumnNames(i).TableField.FormatDelegate IsNot Nothing Then
                                Try
                                    ' Catch possible improperly formatted delegates
                                    Dim paramType As Type = ColumnNames(i).TableField.FormatDelegate.Method.GetParameters()(0).ParameterType
                                    sValue = ColumnNames(i).TableField.FormatDelegate.DynamicInvoke(Convert.ChangeType(sValue, paramType, Nothing)).ToString()
                                Catch ex As Exception
                                End Try
                            End If
                        End If
                        ' if it's an image, export it
                        If sValue = "(Image)" Then
                            SetTableCellImage(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, ColumnNames(i).TableField.Name))
                        Else
                            oTable.Cell(rowCounter, i + 1).Range.Text = sValue
                        End If
                    Catch ex As Exception
                        Throw ex
                    End Try
                Next
                rowCounter += 1
            Next

        End If

        doc = wordProxy.Document
        wordProxy.ShowDocument()

        Export = doc
    End Function

    ' Exports a IEnumerable collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEnumerable, ColumnNames As List(Of String)) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()

        ' if Word is active then use it
        If Not wordProxy.GetWord() Then
            If Not wordProxy.CreateWord() Then
                Throw New System.Exception("Could not start Microsoft Word.")
            End If
        End If

        wordProxy.OpenDocument(DocumentPath)
        doc = wordProxy.Document

        Export = Export(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection, ColumnNames)
        wordProxy.ShowDocument()
    End Function

    ' Exports a IEnumerable collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEnumerable, ColumnNames As List(Of String)) As Object
        Dim mappings As New List(Of ColumnMapping)
        Dim map As ColumnMapping

        For Each name As String In ColumnNames
            map = New ColumnMapping("", name)
            mappings.Add(map)
        Next

        Export = Export(Document, BookmarkName, StartRow, BuildColumnHeadings, Collection, mappings)
    End Function

    ' Exports a IEnumerable collection to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(DocumentPath As String, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEnumerable, ColumnNames As List(Of ColumnMapping)) As Object
        Dim doc As Object = Nothing
        Dim wordProxy As New WordHelper()

        ' if Word is active then use it
        If Not wordProxy.GetWord() Then
            If Not wordProxy.CreateWord() Then
                Throw New System.Exception("Could not start Microsoft Word.")
            End If
        End If

        wordProxy.OpenDocument(DocumentPath)
        doc = wordProxy.Document

        Export = Export(doc, BookmarkName, StartRow, BuildColumnHeadings, Collection, ColumnNames)
        wordProxy.ShowDocument()
    End Function

    ' Exports an IEnumerable  to a table in given Document. BookmarkName is the name of the bookmark associated
    ' with the table.
    Public Function Export(Document As Object, BookmarkName As String, StartRow As Integer, BuildColumnHeadings As Boolean, Collection As IEnumerable, ColumnNames As List(Of ColumnMapping)) As Object
        Dim entityObj As Object
        Dim count As Integer

        count = (From c In Collection
                 Select c).Count()

        If count > 0 Then

            ' get column properties
            Dim columnCounter As Integer = 1
            Dim rowCounter As Integer = StartRow

            ' validate that the Documnet argument is expected type of Word.Document
            If Not IsWordDocumentObject(Document) Then
                Throw New System.ArgumentException("'Document' is not the expected type of object. Expected object should be a Word.Document object.", "Document")
            End If

            ' validate the BookmarkName argument
            If Not IsValidBookmark(Document, BookmarkName) Then
                Throw New System.ArgumentException("'BookmarkName' was not found in 'Document'", "BookmarkName")
            End If

            ' validate that the bookmark is part of a table
            If Document.Bookmarks(BookmarkName).Range.Tables.Count = 0 Then
                Throw New System.ArgumentException("No table was found at the bookmark", "BookmarkName")
            End If

            ' add table
            Dim oTable As Object
            oTable = Document.Bookmarks(BookmarkName).Range.Tables(1)

            ' validate the StartRow argument
            If StartRow > oTable.Rows.Count Then
                Throw New System.ArgumentException("'StartRow' is greater then the number of rows in the table", "StartRow")
            End If

            ' add columns names to the list
            Dim map As ColumnMapping
            For Each map In ColumnNames
                If columnCounter > oTable.Columns.Count Then
                    oTable.Columns.Add()
                End If
                If BuildColumnHeadings Then
                    If map.TableField.DisplayName.Length > 0 Then
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.DisplayName
                    Else
                        oTable.Cell(rowCounter, columnCounter).Range.Text = map.TableField.Name
                    End If
                End If
                columnCounter += 1
            Next

            ' add values on the row following the headers
            If BuildColumnHeadings Then
                rowCounter += 1
            End If

            ' iterate the collection and extract values by column name
            For Each entityObj In Collection
                For i As Integer = 0 To ColumnNames.Count - 1
                    If rowCounter > oTable.Rows.Count Then
                        oTable.Rows.Add()
                    End If
                    Try
                        Dim sValue As Object

                        ' passed in a static value so use that instead
                        If ColumnNames(i).TableField.StaticValue IsNot Nothing Then
                            sValue = ColumnNames(i).TableField.StaticValue.ToString()
                        Else
                            If TypeOf entityObj Is IBusinessObject Then
                                sValue = LightSwitchHelper.GetValue(entityObj, ColumnNames(i).TableField.Name)
                            Else
                                sValue = LightSwitchHelper.GetObjectValue(entityObj, ColumnNames(i).TableField.Name)
                            End If

                            ' check to see if we have to format the value
                            If ColumnNames(i).TableField.FormatDelegate IsNot Nothing Then
                                Try
                                    ' Catch possible improperly formatted delegates
                                    Dim paramType As Type = ColumnNames(i).TableField.FormatDelegate.Method.GetParameters()(0).ParameterType
                                    sValue = ColumnNames(i).TableField.FormatDelegate.DynamicInvoke(Convert.ChangeType(sValue, paramType, Nothing)).ToString()
                                Catch ex As Exception
                                End Try
                            End If
                        End If
                        ' if it's an image, export it
                        If sValue = "(Image)" Then
                            SetTableCellImage(oTable, rowCounter, i + 1, LightSwitchHelper.GetImage(entityObj, ColumnNames(i).TableField.Name))
                        Else
                            oTable.Cell(rowCounter, i + 1).Range.Text = sValue
                        End If
                    Catch ex As Exception
                        Throw ex
                    End Try
                Next
                rowCounter += 1
            Next

        Else
            ' No items in the collection
        End If

        Export = Document
    End Function


#End Region

    ' Returns a Word.Application object
    Public Function GetWord() As Object
        Dim wordProxy As New WordHelper
        Dim w As Object = Nothing

        ' first try and get a reference to a running instance
        If wordProxy.GetWord Then
            w = wordProxy.Word
        Else
            ' next try and create a new instance
            If wordProxy.CreateWord() Then
                w = wordProxy.Word
            Else
                ' can't get word - return Nothing by default
            End If
        End If
        GetWord = w
    End Function

    ' Returns a Word.Document object
    Public Function GetDocument(Word As Object, DocumentPath As String) As Object
        Dim doc As Object = Nothing

        ' Validate Word argument is actually Word.Application 
        If Not IsWordApplicationObject(Word) Then
            Throw New System.ArgumentException("'Word' is not the expected type of object. Expected object should be a Word.Application object.", "Word")
        End If

        If Not File.Exists(DocumentPath) Then
            Throw New System.ArgumentException("File '" & DocumentPath & "' does not exist.", "DocumentPath")
        End If

        Try
            doc = Word.Documents.Open(DocumentPath)
        Catch ex As Exception
            Throw ex
        End Try

        GetDocument = doc
    End Function

    ' Saves the Document as PDF to the path and file name supplied by FullName
    ' Optionally shows the PDF document after it is created
    Public Sub SaveAsPDF(Document As Object, FullName As String, ShowPDF As Boolean)
        Dim bSaved As Boolean = False
        Try
            Document.SaveAs(FullName, 17)  ' WdSaveFormat.wdFormatPDF
            bSaved = True
            If ShowPDF Then Document.FollowHyperlink(FullName)
        Catch comException As System.Runtime.InteropServices.COMException
            Select Case comException.ErrorCode
                Case Is = -2146824090  ' Adobe Reader or equivalent not found
                    ' Two known conditions will generate this exception - when
                    ' a bad path is provided in the FullName parameter or 
                    ' when the document is saved but Adobe Reader isn't installed
                    If bSaved Then
                        Throw New System.ArgumentException("Adobe Reader or equivalent not found", "ShowPDF")
                    Else
                        Throw New System.ArgumentException("An exception occured while attempting to save as PDF. Please ensure that the file path is correct.", "FullName")
                    End If
                Case Else
                    Throw comException
            End Select
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

    Private Function IsWordDocumentObject(doc As Object) As Boolean
        Dim isValid As Boolean = False
        Dim i As Integer
        Try
            i = doc.ContentControls.Count
            isValid = True
        Catch ex As Exception
        End Try
        IsWordDocumentObject = isValid
    End Function

    Private Function IsWordApplicationObject(app As Object) As Boolean
        Dim isValid As Boolean = False
        Dim s As String
        Try
            s = app.Name
            If s = "Microsoft Word" Then
                isValid = True
            End If
        Catch ex As Exception
        End Try
        IsWordApplicationObject = isValid
    End Function

    Private Function IsValidBookmark(doc As Object, sBookmark As String) As Boolean
        Dim isValid As Boolean = False
        For Each b As Object In doc.Bookmarks
            If b.Name = sBookmark Then
                isValid = True
                Exit For
            End If
        Next
        IsValidBookmark = isValid
    End Function

    Private Sub PopulateContentControls(columnMappings As List(Of ColumnMapping), item As IEntityObject, wordProxy As WordHelper)
        For Each mapping As ColumnMapping In columnMappings

            If mapping.TableField IsNot Nothing AndAlso mapping.TableField.Name <> "<Ignore>" Then
                If mapping.TableField.StaticValue IsNot Nothing Then
                    wordProxy.SetContentControl(mapping.OfficeColumn, mapping.TableField.StaticValue.ToString())
                Else

                    Dim value As String = LightSwitchHelper.GetValue(item, mapping.TableField.Name)

                    If value = "(Image)" Then
                        Dim imgData() As Byte = item.Details.Properties(mapping.TableField.Name).Value

                        wordProxy.SetContentControl(mapping.OfficeColumn, imgData)
                    Else
                        If mapping.TableField.FormatDelegate IsNot Nothing Then
                            Try
                                ' Catch possible improperly formatted delegates
                                Dim paramType As Type = mapping.TableField.FormatDelegate.Method.GetParameters()(0).ParameterType
                                value = mapping.TableField.FormatDelegate.DynamicInvoke(Convert.ChangeType(value, paramType, Nothing)).ToString()
                            Catch ex As Exception
                            End Try
                        End If

                        wordProxy.SetContentControl(mapping.OfficeColumn, value)
                    End If
                End If
            End If
        Next
    End Sub

    Private Sub SetTableCellImage(Table As Object, Row As Int32, Column As Int32, Value As Byte())
        If Not Value Is Nothing Then
            Dim range As Object = Table.Cell(Row, Column).Range
            If range.InlineShapes.Count > 0 Then
                range.InlineShapes(1).Delete()
            End If
            Dim imageFilePath As String = CreateFile(Value)
            Table.Application.ActiveDocument.InlineShapes.AddPicture(imageFilePath, False, True, range)
            File.Delete(imageFilePath)
        End If
    End Sub

    Private Function CreateFile(FileContent As Byte()) As String
        Dim filePath = Path.GetTempFileName()
        Dim fileStream As New FileStream(filePath, FileMode.OpenOrCreate)
        Dim bw As New BinaryWriter(fileStream)
        bw.Write(FileContent)
        bw.Close()
        fileStream.Close()
        CreateFile = filePath
    End Function

End Module

